/*
*  Copyright 2019-2020 Zheng Jie
*
*  Licensed under the Apache License, Version 2.0 (the "License");
*  you may not use this file except in compliance with the License.
*  You may obtain a copy of the License at
*
*  http://www.apache.org/licenses/LICENSE-2.0
*
*  Unless required by applicable law or agreed to in writing, software
*  distributed under the License is distributed on an "AS IS" BASIS,
*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*  See the License for the specific language governing permissions and
*  limitations under the License.
*/
package com.fzhucc.modules.technical.service;

import cn.hutool.core.util.RandomUtil;
import com.fzhucc.exception.BadRequestException;
import com.fzhucc.modules.security.config.bean.LoginProperties;
import com.fzhucc.modules.security.config.bean.SecurityProperties;
import com.fzhucc.modules.security.domain.OnlineUser;
import com.fzhucc.modules.security.domain.bo.WxAuthenticationToken;
import com.fzhucc.modules.security.domain.dto.AuthDto;
import com.fzhucc.modules.security.domain.dto.JwtUserDto;
import com.fzhucc.modules.security.security.TokenProvider;
import com.fzhucc.modules.security.service.AuthenticationService;
import com.fzhucc.modules.security.service.OnlineUserService;
import com.fzhucc.modules.security.service.UserDetailsServiceImpl;
import com.fzhucc.modules.system.domain.User;
import com.fzhucc.modules.system.repository.UserRepository;
import com.fzhucc.modules.system.service.UserService;
import com.fzhucc.modules.system.service.dto.UserDto;
import com.fzhucc.modules.technical.domain.Technical;
import com.fzhucc.modules.technical.domain.dto.TechnicalAddDto;
import com.fzhucc.modules.technical.domain.dto.TechnicalEditDto;
import com.fzhucc.modules.thinktank.domain.Academician;
import com.fzhucc.modules.thinktank.domain.dto.AllBindDto;
import com.fzhucc.modules.thinktank.domain.dto.AllBindDto2;
import com.fzhucc.utils.*;
import lombok.RequiredArgsConstructor;
import com.fzhucc.modules.technical.repository.TechnicalRepository;
import com.fzhucc.modules.technical.domain.dto.TechnicalDto;
import com.fzhucc.modules.technical.domain.dto.TechnicalQueryCriteria;
import com.fzhucc.modules.technical.service.mapstruct.TechnicalMapper;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import java.util.*;
import java.io.IOException;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* @website https://eladmin.vip
* @description 服务实现
* @author yyg
* @date 2022-11-21
**/
@Service
@RequiredArgsConstructor
public class TechnicalService {

    private final TechnicalRepository technicalRepository;
    private final TechnicalMapper technicalMapper;

    private final UserService userService;

    private final WxClient wxClient;

    private final RedisUtils redisUtils;

    private final UserDetailsServiceImpl userDetailsService;

    private final UserRepository userRepository;

    private final AuthenticationService authenticationService;

    private final TokenProvider tokenProvider;

    private final OnlineUserService onlineUserService;

    private final SecurityProperties securityProperties;

    @Resource
    private LoginProperties loginProperties;

    public Map<String,Object> queryAll(TechnicalQueryCriteria criteria, Pageable pageable){
        Page<Technical> page = technicalRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable);
        Page<TechnicalDto> map = page.map(technicalMapper::toDto);
        List<TechnicalDto> list = map.getContent();
        for (int i = 0; i < list.size(); i++) {
            TechnicalDto technicalDto = list.get(i);
            Long createUserId = technicalDto.getCreateUserId();
            UserDto userDto = userService.findById(createUserId);
            if (userDto != null) {
                technicalDto.setCreateUser(userDto);
            }
        }
        return PageUtil.toPage(map);
    }

    public List<TechnicalDto> queryAll(TechnicalQueryCriteria criteria){
        return technicalMapper.toDto(technicalRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder)));
    }

    public TechnicalDto findById(Integer id) {
        Technical technical = technicalRepository.findById(id).orElseGet(Technical::new);
        ValidationUtil.isNull(technical.getId(),"Technical","id",id);
        return technicalMapper.toDto(technical);
    }

    @Transactional(rollbackFor = Exception.class)
    public TechnicalDto create(TechnicalAddDto technicalAddDto) {
        Technical resources = new Technical();
        Long userId = SecurityUtils.getCurrentUserId();
        if (userId == null) {
            throw new BadRequestException("用户ID未找到");
        }
        resources.setCreateUserId(userId);
        resources.setName(technicalAddDto.getName());
        resources.setIsShow(technicalAddDto.getIsShow());
        resources.setIntro(technicalAddDto.getIntro());
        resources.setAvatar(technicalAddDto.getAvatar());
        resources.setUserId(technicalAddDto.getUserId());
        resources.setPost(technicalAddDto.getPost());
        resources.setUnit(technicalAddDto.getUnit());
        resources.setIsDelete(0);
        resources.setIsPush(technicalAddDto.getIsPush());
        resources.setSort(technicalAddDto.getSort());
        return technicalMapper.toDto(technicalRepository.save(resources));
    }

    @Transactional(rollbackFor = Exception.class)
    public void update(TechnicalEditDto technicalEditDto) {
        Technical resources = new Technical();
        Long userId = SecurityUtils.getCurrentUserId();
        if (userId == null) {
            throw new BadRequestException("用户ID未找到");
        }
        resources.setId(technicalEditDto.getId());
        resources.setCreateUserId(userId);
        resources.setName(technicalEditDto.getName());
        resources.setIsShow(technicalEditDto.getIsShow());
        resources.setIntro(technicalEditDto.getIntro());
        resources.setAvatar(technicalEditDto.getAvatar());
        resources.setUserId(technicalEditDto.getUserId());
        resources.setPost(technicalEditDto.getPost());
        resources.setUnit(technicalEditDto.getUnit());
        resources.setSort(technicalEditDto.getSort());
        resources.setIsPush(technicalEditDto.getIsPush());
        Technical technical = technicalRepository.findById(resources.getId()).orElseGet(Technical::new);
        ValidationUtil.isNull( technical.getId(),"Technical","id",resources.getId());
        technical.copy(resources);
        technicalRepository.save(technical);
    }

    @Transactional(rollbackFor = Exception.class)
    public void deleteAll(Integer[] ids) {
        for (Integer id : ids) {
            //technicalRepository.deleteById(id);
            Optional<Technical> res = technicalRepository.findById(id);
            if (!res.isPresent()){
                throw new BadRequestException("技术经理人未找到");
            }
            Technical technical = res.get();
            technical.setIsDelete(1);
            technicalRepository.save(technical);
        }
    }

    public void download(List<TechnicalDto> all, HttpServletResponse response) throws IOException {
        List<Map<String, Object>> list = new ArrayList<>();
        for (TechnicalDto technical : all) {
            Map<String,Object> map = new LinkedHashMap<>();
            map.put("姓名", technical.getName());
            map.put("简介", technical.getIntro());
            map.put("头像", technical.getAvatar());
            map.put("是否小程序展示", technical.getIsShow());
            map.put("创建者user_id", technical.getCreateUserId());
            map.put("user_id", technical.getUserId());
            map.put("单位", technical.getUnit());
            map.put("职务", technical.getPost());
            map.put("排序", technical.getSort());
            map.put("是否推送", technical.getIsPush());
            list.add(map);
        }
        FileUtil.downloadExcel(list, response);
    }

    /**
     * 生成技术经理人绑定码。绑定码有效时间：1天
     *
     * @param technicalId 技术经理人ID
     * @return AllBindDto
     */
    public AllBindDto generateQrCode(Integer technicalId) {
        if (!technicalRepository.existsById(technicalId)) {
            throw new BadRequestException("技术经理人[id:" + technicalId + "]不存在");
        }
        String code = RandomUtil.randomString(10);
        String qrcode = wxClient.generateMpQr(
                null,
                "type=bindJs&code=" + code
        );
        String url = wxClient.generateUrlLink(
                null,
                "type=bindJs&code=" + code,
                86400000L
        );
        redisUtils.set("technical:bind:" + code, technicalId, 86400);
        AllBindDto dto = new AllBindDto();
        dto.setExpireTime(86400000L);
        dto.setQrcode(qrcode);
        dto.setUrl(url);
        return dto;
    }

    public TechnicalDto findTechnicalByCode(String code) {
        Integer technicalId = (Integer) redisUtils.get("technical:bind:" + code);
        if (technicalId == null) {
            throw new BadRequestException("技术经理人邀请码不存在");
        }
        final Technical technical = technicalRepository.findById(technicalId).orElseThrow(() ->
                new BadRequestException("技术经理人[id:" + technicalId + "]不存在")
        );
        return technicalMapper.toDto(technical);
    }

    @Transactional
    public AuthDto bindTechnical(AllBindDto2 dto, HttpServletRequest request) {
        Integer technicalId = (Integer) redisUtils.get("technical:bind:" + dto.getCode());
        if (technicalId == null) {
            throw new BadRequestException("技术经理人邀请码不存在");
        }
        JwtUserDto currentUser;
        try {
            currentUser = userDetailsService.loadUserByUsername(JwtSecurityUtils.getCurrentUsername());
            final Long userId = currentUser.getUser().getId();
            // 若已绑定其他技术经理人，则先取消原先绑定的技术经理人
            Technical technical = technicalRepository.findByUserId(userId);
            if (technical != null) {
                technical.setUserId(null);
                technicalRepository.save(technical);
            }
        } catch (UsernameNotFoundException e) {
            // 用户未登录，则走登录流程
            if (dto.getWxCode() == null) {
                throw new BadRequestException("用户信息未找到");
            }
            WxAuthenticationToken wxAuthentication = authenticationService.authenticateByWxMpCode(dto.getWxCode(), 7);
            currentUser = wxAuthentication.getUser();
        }
        Technical technical = technicalRepository.findById(technicalId).orElseThrow(() ->
                new BadRequestException("技术经理人[id:" + technicalId + "]不存在")
        );
        Long currentUserId = currentUser.getUser().getId();
        technical.setUserId(currentUserId);
        technicalRepository.save(technical);
        if (currentUser.getUser().getRoles().stream().noneMatch(s -> s.getId() == 7)) {
            userService.grantRole(currentUserId, 7L);
        }
        redisUtils.del("technical:bind:" + dto.getCode());

        // 权限修改，返回新的token
        User user = userRepository.findByUsername(currentUser.getUsername());
        WxAuthenticationToken authentication = authenticationService.authenticateByUnionId(user.unionId);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        String apiToken = tokenProvider.createToken(authentication);
        JwtUserDto jwtUserDto = authentication.getPrincipal();
        // 保存在线信息
        onlineUserService.save(jwtUserDto, OnlineUser.SYSTEM, apiToken, request);
        // 返回 token 与 用户信息
        AuthDto authDto = new AuthDto(
                securityProperties.getTokenStartWith() + apiToken,
                jwtUserDto
        );
        if (loginProperties.isSingleLogin()) {
            //踢掉之前已经登录的token
            onlineUserService.checkLoginOnUser(authDto.getUser().getUsername(), apiToken);
        }
        return authDto;
    }
}